const std = @import("std");
const Step = std.Build.Step;
const builtin = @import("builtin");
const zon = @import("build.zig.zon");
const String = []const u8;

// check minimum zig version
comptime {
    const current_zig = builtin.zig_version;
    const min_zig = std.SemanticVersion.parse(zon.minimum_zig_version) catch unreachable;
    if (current_zig.order(min_zig) == .lt) {
        @compileError(std.fmt.comptimePrint(
            "Your Zig version v{} does not meet the minimum build requirement of v{}",
            .{ current_zig, min_zig },
        ));
    }
}

pub fn build(b: *std.Build) !void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    const install_prefix = b.install_prefix[b.build_root.path.?.len + 1 ..];

    // Build Jack Compilers
    const build_tls = b.step("build", "Build HACK Assembler, VM Translator and Jack Compilers");
    const targets = [_]struct { String, String }{
        .{ "hardware/06-HackAssembler/", "HackAssembler" },
        .{ "software/vm/07-processing/", "VMTranslator1" },
        .{ "software/vm/08-control/", "VMTranslator2" },
        .{ "software/compiler/10-JackAnalyzer/", "JackAnalyzer" },
        .{ "software/compiler/11-JackCompiler/", "JackCompiler" },
    };

    var compilers: [targets.len]*Step.Compile = undefined;
    for (targets, 0..) |t, i| {
        const install_app, const app = buildExe(b, t[0], .{ .name = t[1], .target = target, .optimize = optimize });
        build_tls.dependOn(install_app);
        compilers[i] = app;
    }

    b.default_step.dependOn(build_tls);

    // Compile OS jack source files
    const os_tls = b.step("os", "Compile OS implementation");
    const compile_os = b.addRunArtifact(compilers[4]);
    compile_os.addArgs(&.{ "software/12-os/", b.pathJoin(&.{ install_prefix, "OS" }) });
    os_tls.dependOn(&compile_os.step);

    // Compile Snake Game
    const run_compiler = b.addRunArtifact(compilers[4]);
    const snake_path = b.pathJoin(&.{ install_prefix, "Snake" });
    run_compiler.addArgs(&.{ "software/09-jack-lang/Snake/", snake_path });

    // Translate Snake vm files
    const run_translator = b.addRunArtifact(compilers[2]);
    run_translator.addArg(snake_path);
    run_translator.step.dependOn(&run_compiler.step);

    // Assemble Snake.asm
    const asm_path = b.pathJoin(&.{ snake_path, "Snake.asm" });
    const run_assembler = b.addRunArtifact(compilers[0]);
    run_assembler.addArg(asm_path);
    run_assembler.step.dependOn(&run_translator.step);

    const snake_tls = b.step("snake", "Compile Snake Game");
    snake_tls.dependOn(&run_assembler.step);

    // native target
    if (target.result.os.tag == builtin.os.tag) {
        b.default_step.dependOn(os_tls);
        b.default_step.dependOn(snake_tls);
    }
}

/// Create a install step and a Compile step for a directory
fn buildExe(owner: *std.Build, src_dir: String, opt: std.Build.ExecutableOptions) struct { *Step, *Step.Compile } {
    const source_file = owner.fmt("{s}/main.zig", .{src_dir});
    const app = owner.addExecutable(.{
        .name = opt.name,
        .root_source_file = owner.path(source_file),
        .target = opt.target,
        .optimize = opt.optimize,
    });

    const install_app = owner.addInstallArtifact(app, .{
        .dest_dir = .{ .override = .{ .custom = "" } },
    });

    return .{ &install_app.step, app };
}
